package primitive3D;

import static rayCaster.ApplicationInterface.*;
import static helper.Util.*;
import javax.vecmath.Point3d;
import javax.vecmath.Vector3d;
import rayCaster.Ray;


public class Triangle extends Primitive3D {
	private Point3d a, b, c;

	// El orden en que se recorren los puntos del triangulo es:
	// Inicia en a, sigue por b y luego por c, en sentido contra reloj.
	public Triangle( Point3d a, Point3d b, Point3d c ){
		this.a = new Point3d(a);
		this.b = new Point3d(b);		
		this.c = new Point3d(c);
		name = new String("");
		components = null;
		color = TRIANGLE_COLOR;
		//Set position as the intersection of Medians
		position = new Point3d(this.a);
		position.add(this.b);
		position.add(this.c);
		position.scale(1.0/3.0);		
	}
	
	public Triangle( Point3d[]points ){
		this(points[0], points[1], points[2]);
	}
	
	public void setPosition(Point3d otherPosition) {
		//First move it to the origin
		a.sub(position);
		b.sub(position);
		c.sub(position);
		
		//Now move it to the new position
		a.add(otherPosition);
		b.add(otherPosition);
		c.add(otherPosition);
		position = new Point3d(otherPosition);
	}	
	
	@Override
	public Object clone(){
		Triangle obj=null;
        try{
            obj=(Triangle)super.clone();
        }catch(CloneNotSupportedException ex){
            System.out.println("No se puede duplicar");
        }        
        obj.a = (Point3d)a.clone();
        obj.b = (Point3d)b.clone();
        obj.c = (Point3d)c.clone();        
        return obj;	    
	}
	
	public String toString(){
		StringBuffer str = new StringBuffer();
		
		str.append("Triangle \"" + getName() + "\"\nA = " + a + "\tB = " + b + "\tC = " + c + "\nPosition = " + position + "\nColor = " + color);
		return str.toString();
	}
	
	@Override
	public void scaleFromReferencePosition(Point3d referencePosition, double scaleFactor) {
		this.scalePointfromReferencePosition(a, referencePosition, scaleFactor);
		this.scalePointfromReferencePosition(b, referencePosition, scaleFactor);
		this.scalePointfromReferencePosition(c, referencePosition, scaleFactor);
	}
	
	public Point3d[] getPoints() {
		return new Point3d[]{a, b, c};
	}

	public Vector3d getFirstVector() {
		// Devuelve el vector a: a-c
		// c se toma como origen de coordenadas
		return getVector(c, a);
	}
	
	public Vector3d getSecondVector() {
		// Devuelve el vector b: b-c
		return getVector(c, b);
	}
	
	public Vector3d getVecNormal() {
		return helper.Util.getVecNormal(getFirstVector(), getSecondVector());
	}
	
	public double area() {
		Vector3d vecNormal = getVecNormal();
		return vecNormal.length()/2;
	}
	
	// Devuelve la cantidad de puntos intersectados. En este caso solo puede
	// devolver 1 o cero. Si hubo punto de interseccion, dicho punto se
	// devuelve en pts[0]. Sino, pts no esta definido o contiene basura.
	public int intersects(Ray ray, Point3d[] pts) throws Exception {

		// Calculo el punto de interseccion entre un plano y el rayo usando:
		// k = -(vecNormal.OrigenRayo + d)/(vecNormal.DirRayo)

		// Calculo el vector normal al plano que contiene al triangulo
		Vector3d vecNormal = getVecNormal();

		// Si el denominador da 0 es porque el rayo es paralelo al plano,
		// entonces o no lo corta, o lo corta en infinitos puntos
		double denominador = vecNormal.dot(ray.getVecDir());
		if( denominador == 0 )
			return 0;

		// d = - vecNormal.PtoDelPlano, a es un pto del triangulo 
		Vector3d vecPoint = new Vector3d(a);
		double d = -vecNormal.dot(vecPoint);

		// Para que el rayo corte al triangulo debe ser k>=0
		Vector3d vecOrigin = new Vector3d(ray.getOrigin());
		double k = -(vecNormal.dot(vecOrigin) + d)/denominador;
		if( k < 0 )
			return 0;

		// Finalmente calculo el punto de interseccion usando:
		// p = origin + k*DirRayo, k >= 0
		ray.getPoint(k, pts[0]);

		// Me fijo si dicho punto esta dentro del triangulo
		int cantIntersectados = 0;
		if( insideTriangle(pts[0]) ) {
			cantIntersectados = 1;
		}
		
		return cantIntersectados;
	}

	// Informa si el punto p se encuentra adentro del triangulo (a,b,c).
	private boolean insideTriangle(Point3d p) {
		// Para que p se encuentre adentro del triangulo (a,b,c) los 3
		// triangulitos de prueba (a,b,p) (b,c,p) (c,a,p) deben tener la
		// misma orientacion que el triangulo (a,b,c).
		return orientation (a, b, p) >= 0 && orientation (b, c, p) >= 0
				&& orientation (c, a, p) >= 0;
	}

	// El resultado devuelto puede ser positivo o negativo dependiendo de la
	// orientacion de los puntos del triangulo.
	private double orientation(Point3d a, Point3d b, Point3d c) {
		// Calculo la normal del triangulito de prueba
		Triangle t = new Triangle(a, b, c);
		Vector3d vecNormalTest = t.getVecNormal();
		if( vecNormalTest.length() == 0 )
			return 0;
		// Calculo la normal del triangulo (a,b,c)
		Vector3d vecNormalTriangle = getVecNormal();
		// Si los vectores normales tienen la misma orientacion devuelve > 0,
		// sino tienen direcciones opuestas y devuelve < 0.
		return vecNormalTriangle.dot(vecNormalTest);
	}

}
